/***************************************************************************
 *
 *   copyright            : (C) 2005 Winds of Storm
 *
 *   $Id: chat.c,v 1.23 2007/02/10 12:41:26 nathan Exp $
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

/*
 *
 * Based on selectserver.c from "Beej's Guide to Network Programming"
 *
 *       http://www.ecst.csuchico.edu/~beej/guide/net/
 *
 * Also based on simple_mutex.c from "Pthreads Programming"
 *
 *       http://www.oreilly.com/catalog/pthread/
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netinet/tcp.h>
#ifdef __sun__
#include <sys/file.h>
#endif
#include "sb.h"

#ifndef TEMP_FAILURE_RETRY
# define TEMP_FAILURE_RETRY(expression) \
  (__extension__                                                              \
    ({ long int __result;                                                     \
       do __result = (long int) (expression);                                 \
       while (__result == -1L && errno == EINTR);                             \
       __result; }))
#endif

//
// external variables
//
DESCRIPTOR_DATA         *descriptor_list;
int			listener;

/* This becomes the main() of the new thread.  It
 *    is requred to return void * and take void * as a
 *       parameter. */
void *thread_function(void *arg) {
	int		connected = 1;
	fd_set		my_fdset;
	fd_set		read_fdset;
	fd_set		excp_fdset;
	DESCRIPTOR_DATA	*mydesc = arg;
	struct		timeval tv;
	IO_BUFFER	*tmp_buffer;

	if (mydesc == NULL) {
		sb_log(LOG_ERR, "thread passed empty descriptor");
		pthread_exit(0);
	}

	db_thread_init(dbconn);
	sb_log(LOG_INFO, "Thread: fd %d running.", mydesc->descriptor);
	   
	FD_ZERO(&my_fdset);
	FD_SET(mydesc->descriptor, &my_fdset);
	while (connected) {
		read_fdset	= my_fdset;
		excp_fdset	= my_fdset;

		/* Wait up to five seconds. */
		tv.tv_sec = 0;
		tv.tv_usec = SELECT_USEC;

		if (select(mydesc->descriptor+1, &read_fdset, NULL, &excp_fdset, &tv) == -1) {
			perror("select");
			exit(1);
		}
		//sb_log(LOG_INFO, "thread: polling fd %d", mydesc->descriptor);

		if (FD_ISSET(mydesc->descriptor, &excp_fdset)) { // exception raised
			sb_log(LOG_INFO, "thread: socket %d generated an exception", mydesc->descriptor);
			error_event_add(mydesc, "exception raised");
			connected	= 0;
		}

		if (connected && !mydesc->session_id && FD_ISSET(mydesc->descriptor, &read_fdset)) { // we got data...
			sb_log(LOG_INFO, "thread: new events on %d", mydesc->descriptor);

			if (!descriptor_read(mydesc)) {
				sb_log(LOG_INFO, "thread: could not read on %d", mydesc->descriptor);
				error_event_add(mydesc, "could not read");
				connected = 0;
			}
		//} else {
			//sb_log(LOG_INFO, "thread: no new events on %d", mydesc->descriptor);
		}

		if (connected && mydesc->session_id) {
			if (mydesc->last_event == 0) {
				sb_log(LOG_INFO, "thread: sending initial events to %d", mydesc->descriptor);
				if (poll_events(mydesc) == -1) {
					error_event_add(mydesc, "could not poll events");
					connected	= 0;
				}
			}

			if (connected && !flush_output(mydesc)) {
				error_event_add(mydesc, mydesc->error_buffer); //"could not flush output");
				connected	= 0;
			}
		} else {
			sb_log(LOG_INFO, "thread: attempting primordial evolution on descriptor %d", mydesc->descriptor);
			poll_from_input_buffer(mydesc);
			if (mydesc->input_buffer) {
				primordial_soup(mydesc, mydesc->input_buffer);
				tmp_buffer              = mydesc->input_buffer;
				mydesc->input_buffer  = tmp_buffer->next;
				free(tmp_buffer->buffer);
				free(tmp_buffer);
			}
		}

		//update_session(mydesc);

		if (mydesc->conn_status == CON_QUITTING) {
			connected	= 0;
		}
	}

	sb_log(LOG_INFO, "Thread: fd %d done.", mydesc->descriptor);
	   
	descriptor_reap(mydesc);
	db_thread_end(dbconn);

	pthread_exit(0);
}
	  
void bootstrap_chat(int port) {
	fd_set	master;			// master file descriptor list
	fd_set	read_fds;		// temp file descriptor list for select()
	struct	sockaddr_in myaddr;     // server address
	int	yes=1;			// for setsockopt() SO_REUSEADDR, below
#ifndef __sun__
	int	idle=300;		// send keepalives after 5 mins TCP_KEEPIDLE
#endif
	struct	timeval tv;
	int	pulse=0;
	
	FD_ZERO(&master);    // clear the master and temp sets
	FD_ZERO(&read_fds);
	
	// get the listener
	if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}
	
	// lose the pesky "address already in use" error message
	if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
		perror("setsockopt SO_REUSEADDR");
		exit(1);
	}
	
	// turn on TCP keepalives
	if (setsockopt(listener, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) == -1) {
		perror("setsockopt SO_KEEPALIVE");
		exit(1);
	}
	
#ifndef __sun__
	if (setsockopt(listener, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(int)) == -1) {
		perror("setsockopt TCP_KEEPIDLE");
		exit(1);
	}
#endif
	
	// bind
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.s_addr = INADDR_ANY;
	myaddr.sin_port = htons(port);
	memset(&(myaddr.sin_zero), '\0', 8);
	if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
		perror("bind");
		exit(1);
	}
	
	// listen
	if (listen(listener, 10) == -1) {
		perror("listen");
		exit(1);
	}
	
	// add the listener to the master set
	FD_SET(listener, &master);
	
	// main loop
	for(;;) {
		//sb_log(LOG_INFO, "updating read_fds and calling select()");
		pulse++;
		read_fds = master; // copy it

		tv.tv_sec = 0;
		tv.tv_usec = SELECT_USEC;

		if (select(listener+1, &read_fds, NULL, NULL, &tv) == -1) {
			perror("listener select");
			exit(1);
		}
	
		if (FD_ISSET(listener, &read_fds)) { // we got one!!
			// handle new connections
			init_new_descriptor(listener);
		}

		if (!(pulse % (60 * PULSE_PER_SECOND))) {
			if (!descriptor_list) {
				// No server push connections... keep the database connection from timing out.
				sb_log(LOG_INFO, "Issuing 60sec idle keepalive to DB server");
				db_noop(dbconn);
			}
		}

		if (!(pulse % PULSE_PER_SECOND)) {
			// Check for new alarms 
			master_poll_alarms();
			// Clean up sessions which have errored, timed out, or idled out
			clean_old_sessions();

			if (descriptor_list) {
				// Grab new events and send them to appropriate connected users
				master_poll_events();
				update_all_sessions();
				pulse	= 0;
			}
		}
	}
}

void init_new_descriptor(int sock_fd) {
	DESCRIPTOR_DATA	 *new_desc;
	struct		sockaddr_in sock;
	int		sock_size = sizeof(sock);
	int		desc;
	int		flags;
	char		buffer[MAX_STRING_LENGTH];
	pthread_t	p_thread;

	//
	// Accept the connection
	//
	getsockname(sock_fd, (struct sockaddr *) &sock, &sock_size);
	if ((desc = accept (sock_fd, (struct sockaddr *) &sock, &sock_size)) < 0) {
		sb_log(LOG_ERR, "init_new_descriptor: accept: %m [%d]", errno);
		return;
	}

	sb_log(LOG_INFO, "init_new_descriptor: new connection %d", desc);

	//
	// Set the appropriate flags we need (note this is equivalent to setting O_NONBLOCK...)
	//
	flags	= fcntl(desc, F_GETFL);
	//sb_log(LOG_ERR, "init_new_descriptor: fcntl(%d, F_GETFL) = %d", desc, flags);
	flags	|= FNDELAY;
	if (fcntl(desc, F_SETFL, flags) < 0) {
		sb_log(LOG_ERR, "init_new_descriptor: fcntl F_SETFL of FNDELAY: %m [%d]", errno);
		return;
	}
	//sb_log(LOG_ERR, "init_new_descriptor: fcntl(%d, F_SETFL, %d)", desc, flags);

	//
	// Set up our internal descriptor data
	//
	new_desc	= malloc(sizeof(DESCRIPTOR_DATA));
	if (new_desc == NULL) {
		sb_log(LOG_ERR, "init_new_descriptor: malloc: %m [%d]", errno);
		return;
	}

	new_desc->descriptor	= desc;
	if (getpeername (desc, (struct sockaddr *) &sock, &sock_size) < 0) {
		// getpeername failed
		sb_log(LOG_WARNING, "init_new_descriptor: getpeername failed!");
		new_desc->host	= strdup("<unknown>");
	} else {
		int	address;

		address	= ntohl(sock.sin_addr.s_addr);
		sprintf(buffer, "%d.%d.%d.%d",
			(address >> 24) & 0xFF,
			(address >> 16) & 0xFF,
			(address >>  8) & 0xFF,
			(address      ) & 0xFF);
		sb_log(LOG_INFO, "Connection from: %s", buffer);
		new_desc->host	= strdup(buffer);
		if (new_desc->host == NULL) {
			sb_log(LOG_ERR, "init_new_descriptor: strdup of address failed: %m [%d]", errno);
			return;
		}
	}

	//
	// Add this descriptor onto the chain of the linked list
	//
	sb_log(LOG_INFO, "grabbing mutex lock to update descriptor_list");
	pthread_mutex_lock(&desc_mutex);
	new_desc->next			= descriptor_list;
	descriptor_list			= new_desc;
	pthread_mutex_unlock(&desc_mutex);
	sb_log(LOG_INFO, "released mutex lock after updating descriptor_list");
	new_desc->conn_status		= CON_GET_SID;
	new_desc->input_buffer		= NULL;
	new_desc->output_buffer		= NULL;
	new_desc->username		= NULL;
	new_desc->read_buffer[0]	= '\0';
	new_desc->last_event		= 0;
	new_desc->session_id		= 0;
	new_desc->user_id		= 0;
	new_desc->room_id		= 0;
	new_desc->idle_warning		= 0;

	add_to_output_queue(new_desc, "HTTP/1.1 200 OK\r\n");
	add_to_output_queue(new_desc, "Server: StormeChat/$Revision: 1.23 $\r\n");

	//
	// NC: XX: Are these really needed?
	//
	add_to_output_queue(new_desc, "Content-Type: multipart/mixed;boundary=\"StormeChat\"\r\n");
	add_to_output_queue(new_desc, "--StormeChat\r\n");

	add_to_output_queue(new_desc, "Content-Type: text/html\r\n\r\n");
	add_to_output_queue(new_desc, "<html>\r\n<head>\r\n\t<link rel=\"stylesheet\" href=\"");
	add_to_output_queue(new_desc, site_url);
	add_to_output_queue(new_desc, "chat.css\" type=\"text/css\">\r\n");
	add_to_output_queue(new_desc, "\t<script type=\"text/javascript\" src=\"");
	add_to_output_queue(new_desc, site_url);
	add_to_output_queue(new_desc, "chat_functions.js\"></script>\r\n");
	add_to_output_queue(new_desc, "</head>\r\n<body class=row2>\r\n");
	add_to_output_queue(new_desc, "\t<script type=\"text/javascript\">\r\n\t\tvar scrollPolicy = \"auto\";\r\n");
	//add_to_output_queue(new_desc, "\t\tdocument.domain='");
	//add_to_output_queue(new_desc, site_domain);
	//add_to_output_queue(new_desc, "'\r\n");
	add_to_output_queue(new_desc, "\t</script>\r\n");

	if (pthread_create(&p_thread, NULL, thread_function, (void *)new_desc) != 0) {
  		fprintf(stderr, "Error creating the thread");
		exit(-1);
	}
	if (pthread_detach(p_thread) != 0) {
		perror("Error detaching thread");
		exit(-1);
	}
}

int descriptor_read(DESCRIPTOR_DATA *desc) {
	int cur_ptr;

	sb_log(LOG_INFO, "descriptor_read called on descriptor id %d (%s)", desc->descriptor, desc->host);

	/*      
	 * Check for overflow. 
	 */   
        cur_ptr = strlen (desc->read_buffer);
	if (cur_ptr >= sizeof (desc->read_buffer)) {
		sb_log(LOG_INFO, "%s input overflow!", desc->host);
		sprintf(desc->error_buffer, "Input overflow in abnormal fashion");
		descriptor_write(desc, "Input overflow in abnormal fashion, closing connection.");
		return FALSE;
	}         

	/*        
	 * Snarf input. 
	 */             
	for (;;) {             
		int new_bytes;  

		new_bytes	= read(desc->descriptor, desc->read_buffer + cur_ptr, sizeof (desc->read_buffer) - cur_ptr);
		sb_log(LOG_ERR, "descriptor_read(%d): %d new bytes.", desc->descriptor, new_bytes);
		if (new_bytes > 0) {
			cur_ptr	+= new_bytes;
		} else if (new_bytes == 0) { 
			sb_log(LOG_ERR, "Socket connection broken, closing.");
			return FALSE;
		} else if (errno == EWOULDBLOCK || errno == EAGAIN || errno == 0) {
			// Not a fatal error, so continue.
			//sb_log(LOG_INFO, "descriptor_read() hit EWOULDBLOCK, breaking");
			break;
		} else { 
			sprintf(desc->error_buffer, "Read from descriptor: %s [%d]", strerror(errno), errno);
			sb_log(LOG_ERR, "Read_from_descriptor %d: %s [%d]", desc->descriptor, strerror(errno), errno);
			return FALSE;
		}

		desc->read_buffer[cur_ptr] = '\0';

		sb_log(LOG_INFO, "size of read_buffer: %d", strlen(desc->read_buffer));
		sb_log(LOG_INFO, "read_buffer: %s", desc->read_buffer);
	}

	return TRUE;
}

int descriptor_write(DESCRIPTOR_DATA *desc, char *message) {
	int	block_size, remaining;
	int	cur_byte;
	int	message_size;

	if ((message == NULL) || (*message == '\0')) {
		sb_log(LOG_ERR, "descriptor_write called with null message");
		return TRUE;
	}

	if (desc->conn_status == CON_QUIT) {
		sb_log(LOG_ERR, "descriptor_write called on closed descriptor %d", desc->descriptor);
		return FALSE;
	}

	message_size	= strlen(message);
	for (cur_byte = 0; cur_byte < message_size; cur_byte += remaining) {
		block_size	= MIN(message_size - cur_byte, MAX_BLOCK_SIZE);
		if ((remaining = TEMP_FAILURE_RETRY(write(desc->descriptor, message + cur_byte, block_size))) < 0) {
			if (errno == EAGAIN) {
				remaining	= 0;
				//sb_log(LOG_ERR, "descriptor_write: write to sock %d hit EAGAIN ", desc->descriptor);
			} else {
				sb_log(LOG_ERR, "descriptor_write: write '%s' to sock %d: %m [%d]", message, desc->descriptor, errno);
				sprintf(desc->error_buffer, "Write on descriptor: %m [%d]", errno);
				//desc->connected = 0;
				return FALSE;
			}
		}
	}

	return TRUE;
}

int descriptor_reap(DESCRIPTOR_DATA *desc) {
	DESCRIPTOR_DATA *tmp_desc;
	DESCRIPTOR_DATA *tmp_desc_next;
	DESCRIPTOR_DATA *tmp_desc_prev;
	int	success = 0;

	sb_log(LOG_INFO, "closing link to: %s", desc->host);
	desc->conn_status	= CON_QUIT;

	sb_log(LOG_INFO, "grabbing mutex lock to update descriptor list");
	pthread_mutex_lock(&desc_mutex);

	tmp_desc_prev	= NULL;
	for (tmp_desc = descriptor_list; tmp_desc; tmp_desc = tmp_desc_next) {
		tmp_desc_next = tmp_desc->next;
		//sb_log(LOG_INFO, "checking desc %d vs. %d", tmp_desc->descriptor, desc->descriptor);

		if (tmp_desc == desc) {
			if (tmp_desc_prev == NULL) {
				//
				// If there is no previous descriptor, set the top of the chain to be
				// the next in line; aka pop the top off.
				//
				descriptor_list		= desc->next;
			} else {
				//
				// If there was a previous descriptor, relink the next link in the chain
				// to the previous link; aka pop out the current link.
				//
				tmp_desc_prev->next	= desc->next;
			}

			sb_log(LOG_INFO, "successfully removed descriptor from linked list");
			success++;

			close(desc->descriptor);
			free(desc->host);
			free(desc);
		}
		tmp_desc_prev	= tmp_desc;
	}

	pthread_mutex_unlock(&desc_mutex);
	sb_log(LOG_INFO, "releasing mutex lock after updating descriptor list");

	return(success);

}

int flush_output(DESCRIPTOR_DATA *desc) {
	IO_BUFFER	*buff_tmp;
	IO_BUFFER	*buff_next;

	if (desc->output_buffer == NULL)
		return TRUE;

	if (desc->conn_status == CON_QUIT)
		return FALSE;

	for (buff_tmp = desc->output_buffer; buff_tmp; buff_tmp = buff_next) {
		buff_next	= buff_tmp->next;
		if (descriptor_write(desc, buff_tmp->buffer)) {
			free(buff_tmp->buffer);
			free(buff_tmp);
			desc->output_buffer	= buff_next;
		} else {
			sb_log(LOG_ERR, "flush_output: could not flush output buffer for descriptor %d", desc->descriptor);
			desc->conn_status	= CON_QUIT;
			return(FALSE);
		}
	}

	return TRUE;
}

int add_to_output_queue(DESCRIPTOR_DATA *desc, char *message) {
	IO_BUFFER	*buff;
	IO_BUFFER	*tmp_buff;
	IO_BUFFER	*last_buff = NULL;	// initialize this to be NULL, to be safe

	if (message == NULL) {
		sb_log(LOG_ERR, "add_to_output_queue: message is NULL!");
		return FALSE;
	}

	if (desc->conn_status == CON_QUIT) {
		sb_log(LOG_ERR, "add_to_output_queue: sending to closed descriptor %d", desc->descriptor);
		return FALSE;
	}

	buff	= malloc(sizeof(IO_BUFFER));
	if (buff == NULL) {
		sb_log(LOG_ERR, "add_to_output_queue: failed to malloc IO_BUFFER: %m [%d]", errno);
		desc->conn_status	= CON_QUIT;
		return FALSE;
	}

	buff->buffer	= strdup(message);
	buff->next	= NULL;

	if (desc->output_buffer != NULL) {
		//
		// Don't loop through the output buffer if it's NULL...
		//
		for (tmp_buff = desc->output_buffer; tmp_buff; tmp_buff = tmp_buff->next) {
			last_buff = tmp_buff;
		}
	}

	if (last_buff == NULL) {
		//
		// No existing output buffer, so stack this on the top.
		//
		desc->output_buffer	= buff;
	} else {
		//
		// Add to end of output buffer chain
		//
		last_buff->next		= buff;
	}

	return TRUE;
}

int poll_from_input_buffer(DESCRIPTOR_DATA *desc) {
	char		*c;
	char		*text;
	char		*last_ptr;
	int		line_size;
	int		offset;
	int		i;
	IO_BUFFER	*tmp_buffer;
	IO_BUFFER	*last_buffer;

	if (desc->read_buffer == NULL)
		return TRUE;

	for (last_buffer = desc->input_buffer; last_buffer; last_buffer = last_buffer->next);

	last_ptr	= desc->read_buffer;
	for (c = desc->read_buffer; *c; *c++) {
		if (*c == '\n') {
			line_size	= (c - last_ptr);
			text		= malloc(line_size);
			if (text == NULL) {
				sb_log(LOG_ERR, "poll_from_input_buffer: malloc failed: %m [%d]", errno);
				return FALSE;
			}
			snprintf(text, line_size, "%s", last_ptr);
			if (mud_verbosity) 
				sb_log(LOG_INFO, "descriptor %d: %s", desc->descriptor, text);
			//descriptor_write(desc, "-- ");
			//descriptor_write(desc, text);
			//descriptor_write(desc, " -- <br />\r\n");

			tmp_buffer		= malloc(sizeof(IO_BUFFER));
			if (tmp_buffer == NULL) {
				sb_log(LOG_ERR, "poll_from_input_buffer: malloc failed: %m [%d]", errno);
				return FALSE;
			}
			tmp_buffer->buffer	= text;
			tmp_buffer->next	= NULL;
			if (last_buffer == NULL) {
				//
				// No previous input buffer, create first link in linked list
				//
				desc->input_buffer	= tmp_buffer;
				last_buffer		= tmp_buffer;
			} else {
				//
				// Previous input buffer, add to end of linked list.
				//
				last_buffer->next	= tmp_buffer;
				last_buffer		= tmp_buffer;
			}
			last_ptr		= c + 1;
		}
	}

	if (*last_ptr == '\0') {
		desc->read_buffer[0]	= '\0';
	} else {
		//sb_log(LOG_INFO, "remaining input buffer: %s", last_ptr);
		offset	= last_ptr - desc->read_buffer;
		for (i = 0; desc->read_buffer[offset + i]; i++) {
			// 
			// Readjust temporary buffer
			//
			desc->read_buffer[i]	= desc->read_buffer[offset + i];
		}
	}

	return TRUE;
}
